<?php
/**
 * | 节程 [ 节程赋能开发者，助力企业发展 ]
 * +----------------------------------------------------------------------
 *  | Copyright (c) 2020~2029 温州惊蛰网络科技有限公司 All rights reserved.
 * +----------------------------------------------------------------------
 *  | Licensed 节程并不是自由软件，未经许可不能去掉节程相关版权
 * +----------------------------------------------------------------------
 */
declare (strict_types=1);

namespace app\madmin\service;

use app\madmin\model\Addons;
use app\madmin\model\AddonsVersion;
use app\madmin\model\Article as admin;
use app\madmin\model\UpdateRecord;
use app\utils\Common;
use app\utils\RedisUtil;
use app\utils\TrimData;
use \GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use think\Cache;
use think\Exception;

use think\facade\Config;
use think\facade\Db;
use think\facade\Filesystem;

class AddonsService
{

    private $mall_id;
    const INSTALL = 0;
    const UPDATE = 1;
    const UNINSTALL = 2;

    const SUCCESS = 1;
    const ERROR = 2;


    private $record;
    private $license;

    private $base;

    private $client;
    //操作 0=安装 1=更新 2=卸载
    private $operate;

    private $root_path;
    private $version = 1.0;
    private $new_version = 2.0;

    public function __construct()
    {
        $this->base = Config::get("base.zongkong_domain", "https://ddu.jingzhe365.com");
        $this->root_path = app()->getRootPath();
        $this->client = new Client();
    }

    /**
     * @return array
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     */
    public function index()
    {
        global $user;
        $addonsList = Db::table("jiecheng_lessee")
            ->where("id", $user->mall_id)
            ->value("addons");
        if ($addonsList)
            $addonsList = json_decode($addonsList, true);
        return [HTTP_SUCCESS, $addonsList];

    }

    //获取总控插件
    public function list()
    {
        # \think\facade\Cache::store('redis')->delete("use_addons_list");
        $cachekey = "use_addons_list";

        $addonsList = \think\facade\Cache::store('redis')->get($cachekey);
        //$addonsList="";
        if (!$addonsList) {
            $client = new Client();
            try {

                $response = $client->request('GET', $this->base . "/addons/saas",
                    [
                        'headers' => [
                            'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
                            'Accept-Encoding' => 'gzip, deflate',
                            'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36'
                        ],
                    ]
                );
            } catch (\GuzzleHttp\Exception\GuzzleException $e) {
                return [HTTP_NOTACCEPT, $e->getMessage()];
            }

            $addonsList = json_decode($response->getBody()->getContents(), true)['msg'];
            $lessee = Db::name("lessee")->where(['status' => 1])->value('addons');
            $plugin_installed = json_decode($lessee);
            $new_list=[];
            foreach ($addonsList as $index => &$item) {
                $item['is_active'] = 0; //是否已激活
                $item['is_support_install'] = 0; //是否不可以安装 1=不可以安装
                $item['is_update'] = 0; //是否可以更新
                $item['is_install'] = 0; //是否已安装
                $addons = Db::name('addons')->where('addons_id', $item['id'])->find();
                if (!$addons) {
                    addons::create([
                        'addons_id' => $item['id'],
                        'name' => $item['name'],
                        'img' => $item['img'],
                        'price' => $item['price'],
                        'status' => 0,
                    ]);
                }
                $saas_addons = Db::name("addons")->where(['addons_id' => $item['id']])->find();
                if (in_array($item['id'], $plugin_installed)) {
                    $item['is_install'] = 1;
                    if(isset($item['version']) && $item['version']>$saas_addons['new_version']){
                        $item['is_update'] = 1;
                    }

                }
                if ($saas_addons && !empty($saas_addons['token'])) {
                    $item['is_active'] = 1;
                    $item['token'] = $saas_addons['token'];
                }
                $item['new_version']=$saas_addons['new_version'] ?? '';
                if (AddonsData::is_support_installd($item['id'])) {
                    $item['is_support_install'] = 1;
                    // $item['is_active'] = 1;
                    // $item['is_install'] = 1;
                    $new_list[]=$item;
                }else{
                    $item['is_active'] = 1;
                    $item['is_install'] = 1;
                    //unset($addonsList[$index]);
                }
            }
            \think\facade\Cache::store('redis')->set($cachekey, $new_list, 60 * 3);

            return [HTTP_SUCCESS, $new_list];

        }
        return [HTTP_SUCCESS, $addonsList];
    }

    //安装插件
    public function install()
    {
        $install_type = request()->post('type', 0);
        if ($install_type == 0 && request()->file()) {
            $local_file = $this->uploadHandler();
        } else {
            $local_file = $this->downloadAddons();
        }
        $addons_id = request()->post('addons_id', 0);

        $this->operate = request()->post("operate", 0);
        //todo 检测插件是否已安装
        $addons = Db::name('addons')->where(['addons_id' => $addons_id])->find();

        if (!AddonsData::is_support_installd($addons_id)) {
            return [HTTP_INVALID, "该插件不支持安装"];
        }
        if ($this->operate == 0 && $addons && $addons['status'] == 2) {
            return [HTTP_INVALID, "插件已安装"];
        }
        //检测是否正在更新
        $updateRecord = UpdateRecord::where(['addons_id' => $addons_id, 'versions' => $this->version, 'type' => 1])
            ->whereNotIn('status', [3, 4])
            ->find();
        if ($updateRecord) {
            return [HTTP_INVALID, "插件正在更新,无需重复执行"];
        }

        $this->version = $addons['new_version'] ?? "1.0.0";
        //todo 检测远程插件并判断本地插件是否最新
        $this->new_version = $this->getAddonsVersion($addons_id);
        if (empty($this->new_version)) {

            return [HTTP_INVALID, "插件版本获取异常,请联系管理员"];
        }
        //建立更新任务
        $updateRecordData = [
            'addons_id' => $addons_id,
            "old_versions" => $this->version,
            "versions" => $this->new_version,
            'content' => "插件准备开始" . ($this->operate == 0 ? '安装' : '更新'),
            'update' => time(),
            'status' => 0
        ];
        $this->record = UpdateRecord::create($updateRecordData);
        Db::startTrans();
        try {
            ## 检测远程插件和本地插件是否是一致 一致就不更了呗
            if ($this->operate == 1 && $this->version == $this->new_version) {
                throw new Exception("版本已是最新，无需更新");
            }
            $this->install_hook((int)$addons_id);
            $this->updateAddons($local_file);
            // db::name('saas_addons')->where(['addons_id' => $addons_id])->update(['new_version' => $this->new_version, 'old_version' => $this->version]);
            $this->record->status = 3;
            $this->record->content = "插件安装成功";
            $this->record->save();

            $this->clearCache();
            $this->writeLog($addons_id, $this->operate, self::SUCCESS, "操作成功");
            Db::commit();
        } catch (Exception $e) {
            Db::rollback();
            $this->writeLog($addons_id, $this->operate, self::ERROR, $e->getMessage());
            $this->record->status = 4;
            $this->record->content = $e->getMessage();
            $this->record->save();

            @unlink($local_file);
            return [$e->getCode(), $e->getMessage()];
        }
        return [HTTP_SUCCESS, '操作成功'];
    }

    //卸载插件
    public function uninstall($addons_id=0)
    {

        try {

            //todo 检测插件是否已安装
//            $addons = Db::name('addons')->where(['status' => 2, 'addons_id' => $addons_id])->find();
//            if (!$addons) {
//                throw new Exception("插件还未安装,请先安装", HTTP_INVALID);
//            }
            if (!AddonsData::is_support_installd($addons_id)) {
                throw new Exception("该插件暂未提供删除", HTTP_NOLICENSE);
            }

            $app_path = app()->getRootPath();
            //首先删除文件
            $addons = "addons_{$addons_id}";
            $addons_files = (new AddonsData())->$addons();
            if (empty($addons_files)) {
                throw new Exception("数据获取异常", HTTP_NOLICENSE);
            }

            foreach ($addons_files as $file) {
                //删除文件
                if (file_exists($app_path . $file)) {
                    unlink($app_path . $file);
                }
            }
            //清除插件注册
            $lessee = Db::name("lessee")->where(['status' => 1])->find();
            $lesseeAddons = json_decode($lessee['addons']);
            if (in_array($addons_id, $lesseeAddons)) {
                foreach ($lesseeAddons as $key => $lesseeAddon) {
                    if ($lesseeAddon == $addons_id) {
                        unset($lesseeAddons[$key]);
                    }
                }
            }
            Db::name('lessee')->where(['status' => 1])->update(['addons' => json_encode(array_values($lesseeAddons))]);
            Db::name('addons')->where(['addons_id' => $addons_id])->update(['status' => 1]);
            Db::commit();
            $this->writeLog($addons_id, self::UNINSTALL, self::SUCCESS, '卸载成功');
            $this->clearCache();
        } catch (Exception $e) {
            $this->writeLog($addons_id, self::UNINSTALL, self::ERROR, $e->getMessage());
            return [$e->getCode(), $e->getMessage()];
        }
        return [HTTP_SUCCESS, '卸载成功'];
    }


    public function updateRecordList($id)
    {
        $page = request()->get('page', 1);
        $size = request()->get('size', 20);
        $data['addons_id'] = $id;
        if (request()->get('status')) {
            $data['status'] = request()->get('status');
        }
        $list = Db::name('update_record')->where($data)->paginate(['page' => $page, 'list_rows' => $size]);
        return [HTTP_SUCCESS, $list];
    }


    public function uploadHandler()
    {

        //上传文件
        $files = request()->file();
        if (!$files) {
            throw new Exception("缺少参数", HTTP_NOLICENSE);
        }
        validate(['file' => 'fileExt:zip|fileMime:application/zip'])
            ->check($files);
        $file = "storage/" . Filesystem::disk('public')->putFile('madmin', $files['file']);
        $local_file = app()->getRootPath() . "public/$file";
        return $local_file;
    }

    //主动下载包更新方案
    public function downloadAddons()
    {
        $dir=app()->getRootPath() . "addons/";
        $local_file = app()->getRootPath() . "addons/install_addons.zip";
        if (!file_exists($dir)) {
            mkdir($dir, 0777, true);
            chmod($dir, 0777);
        }
        $url = "{$this->base}/addons/version/repository/download";
        $addons_id=request()->post('addons_id', 0);
        $addons=Db::name("addons")->where(['addons_id'=>$addons_id])->find();
        if (empty($addons)){
            throw new Exception("插件不存在");
        }
        if (empty($addons['token'])){
            throw new Exception("该插件未激活,无法下载");
        }
        $token= $addons['token'];
        $method = 'post';
        $data = [
            ['name' => 'addons_id',
                'contents' => $addons_id],
            ['name' => 'token', 'contents' => $token],
        ];
        $headers = ['domain' => request()->host()];
        try {
            $response = $this->request($method, $url, $data, $headers);
        } catch (GuzzleException $e) {
            preg_match("{\"msg\":\"(.*)\"}",$e->getMessage(),$r);
            if(isset($r[1])){
                if ($r[1] == "token无效"){
                    Db::name("addons")->where(['addons_id'=>$addons_id])->update(['token'=>'']);
                }
                throw new Exception($r[1], $e->getCode());
            }else{
                throw new Exception("远程校验失败", $e->getCode());
            }
        }
        file_put_contents($local_file, $response);
        return $local_file;
    }


    protected function updateAddons($file)
    {



        //todo 判断开始解析压缩包并执行更新
        $root_path = app()->getRootPath();
        if (!is_dir($root_path . "updater")) {
            mkdir($root_path . "updater");
        }
        $dirs=[
            $root_path."app/utils/scrapy/"
        ];
        foreach($dirs as $path){
            if (!file_exists($path)) {
                mkdir($path, 0777, true);
                chmod($path, 0777);
            }
        }

        $this->deldir($root_path . "updater");
        $updater_path = $root_path . "updater";
        $this->record->status = 1;
        $this->unZip($file, $updater_path);
        // 读取插件目录中的配置文件
        $config = $this->readConfig();

        //todo 处理代码
        $this->record->status = 2;
        if (!is_dir($updater_path . "/app")) {
            throw new Exception("缺少插件代码目录");
        }
        //todo 处理数据库
        $this->execulSql();
        //todo  处理代码
        $this->copy_dir($updater_path . "/app", $root_path . "/app");

        $this->deldir($updater_path . "/app");
        //todo 清除相关缓存
        @unlink($file);
    }

    public function writeLog($addons_id, $type, $status, $content)
    {

        $data = [
            'addons_id' => $addons_id,
            'type' => $type,
            'content' => $content,
            'status' => $status
        ];
        Db::name('addons_log')->insert($data);
        Db::commit();
    }

    //安装时执行
    protected function install_hook(int $addons_id = 0)
    {
        $lessee = Db::name("lessee")->where(['status' => 1])->find();
        if (empty($lessee)) {
            $lesseeAddons = [$addons_id];
        } else {
            $lesseeAddons = array_values(json_decode($lessee['addons'], true));
            if (!in_array($addons_id, $lesseeAddons)) array_unshift($lesseeAddons, (int)$addons_id);
        }
        $lesseeAddons = json_encode($lesseeAddons);
        Db::name('lessee')->where(['status' => 1])->update(['addons' => $lesseeAddons]);
        Db::name('addons')->where(['addons_id' => $addons_id])->update(['status' => 2, 'new_version' => $this->new_version]);
    }

    //清除缓存
    public function clearCache()
    {
        global $user;
        $cache_list = [
            'use_addons_list',
            'use_addons_list*',
            '*use_addons_list*'
        ];
        $redis = new RedisUtil();
        foreach ($cache_list as $key) {
            $keys = $redis->keys($key);
            $redis->del($keys);
        }
        \think\facade\Cache::store('redis')->delete("use_addons_list");
        $lesseeId=$user->mall_id;
        \think\facade\Cache::store('redis')->delete("use_addons_list_" . $lesseeId);
    }

    //执行sql文件更新
    public function execulSql()
    {
        $path = $this->root_path . 'updater/update.sql';
        if (file_exists($path)) {
            $sql_data = file_get_contents($path);
            $sqlList = explode(";", $sql_data);
            //TODO 事务问题
            foreach ($sqlList as $sql) { //遍历数组
                $sql = trim($sql);
                if (empty($sql) || $sql === "/n") {
                    continue;
                }
                $sql .= ";";
                try {
                    Db::execute($sql);
                } catch (Exception $e) {
                    throw new Exception($e->getMessage());
                }
            }
        }
    }

    //读取插件配置文件
    public function readConfig()
    {
        $config_path = $this->root_path . 'updater/config.php';
        return file_exists($config_path) ? include $config_path : [];
    }


    private function unZip($pathToFile, $extractToDir)
    {
        $zip = new \ZipArchive;
        if ($zip->open($pathToFile) === true) {
            //将压缩包文件解压到test目录下
            $zip->extractTo($extractToDir);
            // 关闭zip文件
            $zip->close();
        }
    }

    private function scan_dir($src)
    {
        $dir = opendir($src);
        while (false !== ($file = readdir($dir))) {
            if (($file != '.') && ($file != '..')) {
                if (is_dir($src . '/' . $file)) {
                    $this->scan_dir($src . '/' . $file);
                    continue;
                } else {
                    echo $src . '/' . $file . "<br>";
                }
            }
        }
        closedir($dir);
    }

    private function copy_dir($src, $dst, $update_type = self::INSTALL)
    {
        $dir = opendir($src);
        if (is_dir($dst)) @mkdir($dst);
        while (false !== ($file = readdir($dir))) {
            if (($file != '.') && ($file != '..')) {
                if (is_dir($src . '/' . $file)) {
                    $this->copy_dir($src . '/' . $file, $dst . '/' . $file);
                    continue;
                } else {
                    if ($update_type == self::INSTALL) {
                        //安装的情况下不允许覆盖
                        if (file_exists($dst . '/' . $file)) {
                            continue;
                        }
                    }
                    copy($src . '/' . $file, $dst . '/' . $file);
                }
            }
        }
        closedir($dir);
    }

    private function deldir($dir)
    {
        $dh = opendir($dir);
        while ($file = readdir($dh)) {
            if ($file !== "." && $file !== "..") {
                $fullpath = $dir . "/" . $file;
                if (!is_dir($fullpath)) {
                    unlink($fullpath);
                } else {
                    $this->deldir($fullpath);
                }
            }
        }
        closedir($dh);
//        if (rmdir($dir)) {
//            return true;
//        }

        return false;
    }

    public function getAddonsVersion($addons_id)
    {
        $url = "{$this->base}/addons/version/repository/{$addons_id}";

        $data = $this->request("get", $url);
        $data = json_decode($data, true);


        if (empty($data)) {
            return '1.0.0';
        } else {
            if (!isset($data['msg']['versions'])) {
                return '1.0.0';
            }
            return $data['msg']['versions'];
        }
    }

    public function request($method, $url, $data = [], $header = [])
    {
        $postVersionData = [
            'headers' => $header,
            'multipart' => $data
        ];

        $response = $this->client->request($method, $url, $postVersionData);

        return $response->getBody()->getContents();
    }

}
